package com.weilx959.spt.plugin.e2sp.component;

import com.jfoenix.controls.JFXTextArea;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.input.KeyCode;
import javafx.util.Callback;
import javafx.util.StringConverter;
import javafx.util.converter.DefaultStringConverter;
import lombok.extern.slf4j.Slf4j;

/**
 * 文本区域表单元格
 *
 * @author weilx
 * @since 2024-12-13
 */
@Slf4j
public class TextAreaTableCell<S, T> extends TableCell<S, T> {

    private TextArea textArea;

    /**
     * Provides a {@link TextArea} that allows editing of the cell content when
     * the cell is double-clicked, or when
     * {@link TableView#edit(int, javafx.scene.control.TableColumn)} is called.
     * This method will only  work on {@link TableColumn} instances which are of
     * type String.
     *
     * @param <S> The type of the TableView generic type
     * @return A {@link Callback} that can be inserted into the
     *      {@link TableColumn#cellFactoryProperty() cell factory property} of a
     *      TableColumn, that enables textual editing of the content.
     */
    public static <S> Callback<TableColumn<S,String>, TableCell<S,String>> forTableColumn() {
        return forTableColumn(new DefaultStringConverter());
    }

    /**
     * Provides a {@link TextArea} that allows editing of the cell content when
     * the cell is double-clicked, or when
     * {@link TableView#edit(int, javafx.scene.control.TableColumn) } is called.
     * This method will work  on any {@link TableColumn} instance, regardless of
     * its generic type. However, to enable this, a {@link StringConverter} must
     * be provided that will convert the given String (from what the user typed
     * in) into an instance of type T. This item will then be passed along to the
     * {@link TableColumn#onEditCommitProperty()} callback.
     *
     * @param <S> The type of the TableView generic type
     * @param <T> The type of the elements contained within the TableColumn
     * @param converter A {@link StringConverter} that can convert the given String
     *      (from what the user typed in) into an instance of type T.
     * @return A {@link Callback} that can be inserted into the
     *      {@link TableColumn#cellFactoryProperty() cell factory property} of a
     *      TableColumn, that enables textual editing of the content.
     */
    public static <S,T> Callback<TableColumn<S,T>, TableCell<S,T>> forTableColumn(
        final StringConverter<T> converter) {
        return list -> new TextAreaTableCell<>(converter);
    }

    public TextAreaTableCell() {
        this(null);
    }

    public TextAreaTableCell(StringConverter<T> converter) {
        this.getStyleClass().add("text-area-table-cell");
        setConverter(converter);
    }

    // --- converter
    private final ObjectProperty<StringConverter<T>> converter =
        new SimpleObjectProperty<StringConverter<T>>(this, "converter");

    /**
     * The {@link StringConverter} property.
     * @return the {@link StringConverter} property
     */
    public final ObjectProperty<StringConverter<T>> converterProperty() {
        return converter;
    }

    /**
     * Sets the {@link StringConverter} to be used in this cell.
     * @param value the {@link StringConverter} to be used in this cell
     */
    public final void setConverter(StringConverter<T> value) {
        converterProperty().set(value);
    }

    /**
     * Returns the {@link StringConverter} used in this cell.
     * @return the {@link StringConverter} used in this cell
     */
    public final StringConverter<T> getConverter() {
        return converterProperty().get();
    }

    @Override
    protected void updateItem(T item, boolean empty) {
        super.updateItem(item, empty);

        if (this.isEmpty()) {
            this.setText(null);
            this.setGraphic(null);
        } else {
            if (this.isEditing()) {
                textArea.setText(getItemText());
                this.setText(null);
                this.setGraphic(textArea);
            } else {
                this.setText(getItemText());
                this.setGraphic(null);
            }
        }
    }

    @Override
    public void startEdit() {
        if (! isEditable()
            || ! getTableView().isEditable()
            || ! getTableColumn().isEditable()) {
            return;
        }
        super.startEdit();

        if (isEditing()) {
            if (textArea == null) {
                textArea = createTextArea();
            }
            textArea.setText(getItemText());
            this.setText(null);
            this.setGraphic(textArea);

            textArea.selectAll();
            textArea.requestFocus();
        }
    }

    @Override
    public void cancelEdit() {
        super.cancelEdit();
        this.setText(getItemText());
        this.setGraphic(null);
    }

    private <T> TextArea createTextArea() {
        TextArea jfxTextArea = new JFXTextArea();
        jfxTextArea.setOnKeyReleased(t -> {
            if (t.getCode() == KeyCode.ESCAPE) {
                this.cancelEdit();
                t.consume();
            }
        });
        jfxTextArea.setOnKeyPressed(t -> {
            if (t.isControlDown() && t.getCode() == KeyCode.ENTER) {
                this.commitEdit(getConverter().fromString(jfxTextArea.getText()));
                t.consume();
            }
        });
        jfxTextArea.setPrefRowCount(3);
        return jfxTextArea;
    }

    private String getItemText() {
        if (getConverter() == null) {
            return this.getItem() == null ? "" : this.getItem().toString();
        }
        return getConverter().toString(this.getItem());
    }
}
